package com.easy.frame.base.api.anno.redisLock;

import com.easy.frame.base.api.anno.redisLock.annotation.RedisLock;
import com.easy.frame.base.basic.exception.ServiceException;
import com.easy.frame.common.redis.RedisUtil;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 对加了自定义redis锁注解的任务进行拦截.
 *
 * @author tp
 */
@Aspect
@Component
@Slf4j
public class RedisLockAspect {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 拦截自定义的redis锁注解.
     * com.easy.frame.base.api.anno.redisLock.annotation.RedisLock
     */
    @Pointcut("@annotation(com.easy.frame.base.api.anno.redisLock.annotation.RedisLock)")
    public void pointCut() {
        System.out.println("redis分布式锁初始化注解切入点...");
    }

    /**
     * 环绕通知.
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) {
        //获取方法
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        //获取方法上的注解
        RedisLock annotation = method.getAnnotation(RedisLock.class);
        //获取任务名称
        String key = annotation.key();
        long expireTime = annotation.expireTime();
        String keyValue = getLock(key, expireTime);
        if (StringUtil.isNullOrEmpty(keyValue)) {
            // 获取锁失败。
            throw new ServiceException("重复请求接口，请勿重复操作");
        }
        // 获取锁成功
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throw new ServiceException("redis系统异常1xxx1");
        } finally {
            // 释放锁。
            unlockLua(key, keyValue);
        }
    }

    /**
     * 加锁，自旋重试三次
     *
     * @param key 锁实体
     * @return true
     */
    public String getLock(String key, long expireTime) {
        String value = UUID.randomUUID().toString();
        int tryCount = 3;
        try {
            while (tryCount > 0) {
                Boolean aBoolean = RedisUtil.getInstance().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
                if (aBoolean != null && aBoolean) {
                    return value;
                }
                // 获取锁失败。
                log.error("线程被中断" + Thread.currentThread().getId());
                tryCount--;
            }
        } catch (Exception e) {
            log.error("线程被中断" + Thread.currentThread().getId(), e);
        }
        return value;
    }

    /**
     * 使用lua脚本解锁，不会解除别人锁
     *
     * @param key 锁实体
     */
    public void unlockLua(String key, String value) {
        if (key == null || value == null) {
            return;
        }
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        //用于解锁的lua脚本位置
        boolean unLockStat = redisTemplate.execute((RedisCallback<Boolean>) connection ->
                connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1,
                        key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8"))));
        try {
            if (!unLockStat) {
                log.error("释放分布式锁失败，key={}，已自动超时，其他线程可能已经重新获取锁", key);
            }
        } catch (Exception e) {
            log.error("释放分布式锁失败，key={}", key, e);
        }
    }
}
